001 /*
002 * Copyright 2005 Stephen J. McConnell.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
013 * implied.
014 *
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018
019 package net.dpml.depot;
020
021 import java.io.IOException;
022 import java.rmi.server.RMIClassLoader;
023 import java.rmi.server.RMIClassLoaderSpi;
024 import java.net.MalformedURLException;
025 import java.net.URL;
026 import java.net.URLClassLoader;
027 import java.security.Permission;
028 import java.util.Collections;
029 import java.util.IdentityHashMap;
030 import java.util.Map;
031
032 import net.dpml.lang.StandardClassLoader;
033
034 /**
035 * The DepotRMIClassLoaderSpi handles the loading of classes that are based on
036 * plugin artifact types.
037 * @author <a href="http://www.dpml.net">Digital Product Meta Library</a>
038 * @version 1.0.2
039 */
040 public class DepotRMIClassLoaderSpi extends RMIClassLoaderSpi
041 {
042 private static final Map LOADERS = Collections.synchronizedMap( new IdentityHashMap( 5 ) );
043
044 private RMIClassLoaderSpi m_delegate = RMIClassLoader.getDefaultProviderInstance();
045
046 static
047 {
048 for( ClassLoader classloader = ClassLoader.getSystemClassLoader();
049 classloader != null; classloader = classloader.getParent() )
050 {
051 LOADERS.put( classloader, null );
052 }
053 }
054
055 /**
056 * Default constructor.
057 */
058 public DepotRMIClassLoaderSpi()
059 {
060 super();
061 }
062
063 /**
064 * Provides the implementation for
065 * {@link RMIClassLoader#loadClass(URL,String)},
066 * {@link RMIClassLoader#loadClass(String,String)}, and
067 * {@link RMIClassLoader#loadClass(String,String,ClassLoader)}.
068 *
069 * Loads a class from a codebase URL path, optionally using the
070 * supplied loader.
071 *
072 * @param codebase the list of URLs (separated by spaces) to load
073 * the class from, or <code>null</code>
074 * @param name the name of the class to load
075 * @param defaultLoader additional contextual class loader
076 * to use, or <code>null</code>
077 * @return the <code>Class</code> object representing the loaded class
078 * @exception MalformedURLException if <code>codebase</code> is
079 * non-<code>null</code> and contains an invalid URL, or
080 * if <code>codebase</code> is <code>null</code> and the system
081 * property <code>java.rmi.server.codebase</code> contains an
082 * invalid URL
083 * @exception ClassNotFoundException if a definition for the class
084 * could not be found at the specified location
085 */
086 public Class loadClass(
087 String codebase, String name, ClassLoader defaultLoader )
088 throws MalformedURLException, ClassNotFoundException
089 {
090 //if( null != codebase )
091 //{
092 // final String message =
093 // "Loading class: "
094 // + name
095 // + "\nCodebase: "
096 // + codebase;
097 // getLogger().debug( message );
098 //}
099 return m_delegate.loadClass( codebase, name, defaultLoader );
100 }
101
102 /**
103 * Provides the implementation for
104 * {@link RMIClassLoader#loadProxyClass(String,String[],ClassLoader)}.
105 *
106 * Loads a dynamic proxy class (see {@link java.lang.reflect.Proxy}
107 * that implements a set of interfaces with the given names
108 * from a codebase URL path, optionally using the supplied loader.
109 *
110 * <p>An implementation of this method must either return a proxy
111 * class that implements the named interfaces or throw an exception.
112 *
113 * @param codebase the list of URLs (space-separated) to load
114 * classes from, or <code>null</code>
115 * @param interfaces the names of the interfaces for the proxy class
116 * to implement
117 * @param defaultLoader additional contextual class loader
118 * to use, or <code>null</code>
119 * @return a dynamic proxy class that implements the named interfaces
120 * @exception MalformedURLException if <code>codebase</code> is
121 * non-<code>null</code> and contains an invalid URL, or
122 * if <code>codebase</code> is <code>null</code> and the system
123 * property <code>java.rmi.server.codebase</code> contains an
124 * invalid URL
125 * @exception ClassNotFoundException if a definition for one of
126 * the named interfaces could not be found at the specified location,
127 * or if creation of the dynamic proxy class failed (such as if
128 * {@link java.lang.reflect.Proxy#getProxyClass(ClassLoader,Class[])}
129 * would throw an <code>IllegalArgumentException</code> for the given
130 * interface list)
131 */
132 public Class loadProxyClass(
133 String codebase, String[] interfaces, ClassLoader defaultLoader )
134 throws MalformedURLException, ClassNotFoundException
135 {
136 //if( null != codebase )
137 //{
138 // getLogger().debug( "Loading proxy: " + codebase );
139 //}
140 return m_delegate.loadProxyClass( codebase, interfaces, defaultLoader );
141 }
142
143 /**
144 * Provides the implementation for
145 * {@link RMIClassLoader#getClassLoader(String)}.
146 *
147 * Returns a class loader that loads classes from the given codebase
148 * URL path.
149 *
150 * <p>If there is a security manger, its <code>checkPermission</code>
151 * method will be invoked with a
152 * <code>RuntimePermission("getClassLoader")</code> permission;
153 * this could result in a <code>SecurityException</code>.
154 * The implementation of this method may also perform further security
155 * checks to verify that the calling context has permission to connect
156 * to all of the URLs in the codebase URL path.
157 *
158 * @param codebase the list of URLs (space-separated) from which
159 * the returned class loader will load classes from, or <code>null</code>
160 * @return a class loader that loads classes from the given codebase URL
161 * path
162 * @exception MalformedURLException if <code>codebase</code> is
163 * non-<code>null</code> and contains an invalid URL, or
164 * if <code>codebase</code> is <code>null</code> and the system
165 * property <code>java.rmi.server.codebase</code> contains an
166 * invalid URL
167 * @exception SecurityException if there is a security manager and the
168 * invocation of its <code>checkPermission</code> method fails, or
169 * if the caller does not have permission to connect to all of the
170 * URLs in the codebase URL path
171 */
172 public ClassLoader getClassLoader( String codebase )
173 throws MalformedURLException, SecurityException
174 {
175 return m_delegate.getClassLoader( codebase );
176 }
177
178 /**
179 * Provides the implementation for
180 * {@link RMIClassLoader#getClassAnnotation(Class)}.
181 *
182 * Returns the annotation string (representing a location for
183 * the class definition) that RMI will use to annotate the class
184 * descriptor when marshalling objects of the given class.
185 *
186 * @param cl the class to obtain the annotation for
187 * @return a string to be used to annotate the given class when
188 * it gets marshalled, or <code>null</code>
189 * @exception NullPointerException if <code>cl</code> is <code>null</code>
190 */
191 public String getClassAnnotation( Class cl ) throws NullPointerException
192 {
193 final String annotations = getAnnotation( cl );
194 //if( null != annotations )
195 //{
196 // System.out.println( "# " + cl.getName() + ", " + annotations );
197 //}
198 return annotations;
199 }
200
201 private String getAnnotation( Class cl ) throws NullPointerException
202 {
203 String classname = cl.getName();
204 int i = classname.length();
205 if( ( i > 0 ) && classname.charAt( 0 ) == '[' )
206 {
207 int j;
208 for( j=1; i > j && classname.charAt( j ) == '['; j++ )
209 {
210 if( ( i > j ) && classname.charAt( j ) != 'L' )
211 {
212 return null;
213 }
214 }
215 }
216
217 ClassLoader classloader = cl.getClassLoader();
218 if( classloader == null || LOADERS.containsKey( classloader ) )
219 {
220 return System.getProperty( "java.rmi.server.codebase" );
221 }
222
223 String annotations = null;
224 if( classloader instanceof StandardClassLoader )
225 {
226 annotations = ( (StandardClassLoader) classloader ).getAnnotations();
227 }
228 else if( classloader instanceof URLClassLoader )
229 {
230 annotations = getAnnotations( (URLClassLoader) classloader );
231 }
232
233 if( annotations != null )
234 {
235 return annotations;
236 }
237 else
238 {
239 return System.getProperty( "java.rmi.server.codebase" );
240 }
241 }
242
243 private String getAnnotations( URLClassLoader classloader )
244 {
245 StringBuffer buffer = new StringBuffer();
246 return getAnnotations( buffer, classloader );
247 }
248
249 private String getAnnotations( StringBuffer buffer, URLClassLoader classloader )
250 {
251 packAnnotations( buffer, classloader );
252 String result = buffer.toString();
253 return result.trim();
254 }
255
256 private void packAnnotations( StringBuffer buffer, URLClassLoader classloader )
257 {
258 if( ClassLoader.getSystemClassLoader() == classloader )
259 {
260 return;
261 }
262
263 ClassLoader parent = classloader.getParent();
264 if( ( null != parent ) && ( parent instanceof URLClassLoader ) )
265 {
266 packAnnotations( buffer, (URLClassLoader) parent );
267 }
268
269 try
270 {
271 URL[] urls = classloader.getURLs();
272 if( null != urls )
273 {
274 SecurityManager manager = System.getSecurityManager();
275 if( manager != null )
276 {
277 for( int k = 0; k < urls.length; k++ )
278 {
279 Permission permission = urls[k].openConnection().getPermission();
280 if( permission != null )
281 {
282 manager.checkPermission( permission );
283 }
284 }
285 }
286 buffer.append( urlsToPath( urls ) + " " );
287 }
288 }
289 catch( SecurityException e )
290 {
291 boolean ignore = true; // ignore
292 }
293 catch( IOException e )
294 {
295 boolean ignore = true; // ignore
296 }
297 }
298
299 private static String urlsToPath( URL[] urls )
300 {
301 if( urls.length == 0 )
302 {
303 return null;
304 }
305 else if( urls.length == 1 )
306 {
307 final String path = urls[0].toExternalForm();
308 if( !path.startsWith( "file:" ) )
309 {
310 return path;
311 }
312 else
313 {
314 return "";
315 }
316 }
317 StringBuffer buffer = new StringBuffer( urls[0].toExternalForm() );
318 for( int i=1; i < urls.length; i++ )
319 {
320 final String path = urls[i].toExternalForm();
321 if( !path.startsWith( "file:" ) )
322 {
323 buffer.append( ' ' );
324 buffer.append( path );
325 }
326
327 }
328 return buffer.toString();
329 }
330 }